﻿using System.Collections.Generic;

namespace HenIT.Text
{
    /// <summary>
    /// HTML writer to create HTML fragment or documents
    /// </summary>
    public partial class HTMLWriter : HTMLWriterBase
    {
        #region Constructors
        /// <summary>
        /// Iinitialize the tag auto formatting arrays.
        /// </summary>
        protected override void InitializeTagFormatArrays()
        {
            startTagsAutoOnNewLine = new string[]
            {
                "address",
                "blockquote", 
                "code",
                "div",
                "h1","h2","h3","h4","h5","h6",
                "iframe",
                "ol", "ul", "dl", 
                "table", "thead", "tfoot", "tbody", "tr", "td", "th"
            };
            tagsAutoIncDecIndentation = new string[]
            {
                "address",
                "blockquote", 
                "ol", "ul", "dl",
                "table", "thead", "tfoot", "tbody", "tr"
            };
            endTagsAutoOnNewLine = new string[]
            {
                "address",
                "blockquote",             
                "ol", "ul", "dl", 
                "table", "thead", "tfoot", "tbody", "tr", 
            };
            endTagsAutoAppendCRLF = new string[]
            {
                "address",
                "blockquote", 
                "code",
                "h1","h2","h3","h4","h5","h6",
                "iframe",
                "ol", "ul", "dl", 
                "table"
            };
        }
        /// <summary>
        /// Create a new instance of HTMLWriter
        /// </summary>
        public HTMLWriter() : base() { }
        /// <summary>
        /// Create a new instance of HTMLWriter with document title
        /// </summary>
        /// <param name="documentTitle">HTML document title</param>
        public HTMLWriter(string documentTitle) : base(documentTitle) { }
        /// <summary>
        /// Create a new instance of HTMLWriter with document title and enable AutoFormatting and AutoIndentation
        /// </summary>
        /// <param name="documentTitle">HTML document title</param>
        /// <param name="enableAutoFormatting">Should AutoFormatting and AutoIndentation be enabled?</param>
        public HTMLWriter(string documentTitle, bool enableAutoFormatting) : base(documentTitle) 
        {
            AutoFormatting = enableAutoFormatting;
            AutoIndentation = enableAutoFormatting;
        }
        #endregion        

        #region Generic methods
        /// <summary>
        /// Appends plain text to the stream.
        /// </summary>
        /// <param name="text"></param>
        /// <returns>Reference to current instance</returns>
        public HTMLWriter AppendText(string text)
        {
            AppendInternal(text);
            return this;
        }  
        /// <summary>
        /// Appends new line to stream
        /// </summary>
        /// <returns>Reference to current instance</returns>
        public HTMLWriter AppendNewLine()
        {
            AppendNewLineInternal();
            return this;
        }
        /// <summary>
        /// Appends a new line and indentation to stream
        /// </summary>
        /// <returns>Reference to current instance</returns>
        public HTMLWriter AppendNewLineWithIndentation()
        {
            AppendNewLineWithIndentationInternal();
            return this;
        }

        #region Allow custom tags to be written
        /// <summary>
        /// Append opening tag to stream
        /// </summary>
        /// <param name="tagName">Name of tag</param>
        /// <param name="customAttributes">Custom html attributes</param>
        /// <returns>Reference to current instance</returns>
        public HTMLWriter AppendTagStart(string tagName, params CustomAttribute[] customAttributes)
        {
            AppendTagStartInternal(tagName, "", customAttributes);
            return this;
        }
        /// <summary>
        /// Append opening tag to stream
        /// </summary>
        /// <param name="tagName">Name of tag</param>
        /// <param name="className">CSS classname of tag</param>
        /// <param name="customAttributes">Custom html attributes</param>
        /// <returns>Reference to current instance</returns>
        public HTMLWriter AppendTagStart(string tagName, string className = "", params CustomAttribute[] customAttributes)
        {
            AppendTagStartInternal(tagName, className, customAttributes);
            return this;
        }
        /// <summary>
        /// Appends top end tag as stored on the stack
        /// </summary>
        /// <returns>Reference to current instance</returns>
        public HTMLWriter AppendTagEnd()
        {
            if (tags.Count > 0)
            {
                string tagName = tags.Pop();
                AppendTagEndInternal(tagName, false);
            }
            return this;
        }
        /// <summary>
        /// Append end tag to stream
        /// </summary>
        /// <param name="tagName">The tag name to add</param>
        /// <returns>Reference to current instance</returns>
        public HTMLWriter AppendTagEnd(string tagName)
        {
            AppendTagEndInternal(tagName, true);
            return this;
        }
        /// <summary>
        /// Append the top 'tagsToClose' end tags as stored on the stack
        /// </summary>
        /// <param name="tagsToClose">Number of tags to append</param>
        /// <returns>Reference to current instance</returns>
        public HTMLWriter AppendTagEnd(int tagCountToClose)
        {
            for (int i = 0; i < tagCountToClose; i++)
            {
                AppendTagEnd();
            }
            return this;
        }
        /// <summary>
        /// Append all end tags on the stack until 'tagName'. 
        /// Be carefull when there are multiple tags with the same name as 
        ///  this method will only close tags up until the first one it
        ///  encounters!
        /// Also important, it clears all tags on the stack so if the stack
        ///  does not contain the required tag it will be cleared!
        /// </summary>
        /// <param name="tagName">Tag name to find</param>
        /// <returns>Reference to current instance</returns>
        public HTMLWriter AppendAllEndTagsUntil(string tagName)
        {
            while (tags.Count > 0)
            {
                string tagNameToPop = tags.Pop();
                AppendTagEndInternal(tagNameToPop, false);
                if (tagNameToPop == tagName)
                    break;
            }
            return this;
        }
        #endregion
        #endregion        

        #region html tags
        #region Paragraph and plain text
        /// <summary>
        /// Appends an html 'p' tag with content and CSS class name
        /// </summary>
        /// <param name="content">Text content</param>
        /// <param name="className">CSS class name</param>
        /// <returns>Reference to current instance</returns>
        public HTMLWriter AppendParagraph(string content, string className = "")
        {
            return AppendTagStart("p", className).AppendText(content).AppendTagEnd();
        }
        /// <summary>
        /// Appends an html 'p' start tag with CSS class name
        /// </summary>
        /// <param name="className">CSS class name</param>
        /// <returns>Reference to current instance</returns>
        public HTMLWriter AppendParagraphStart(string className = "")
        {
            return AppendTagStart("p", className);
        }
        /// <summary>
        /// Appends an html 'sub' tag with content and CSS class name
        /// </summary>
        /// <param name="content">Text content</param>
        /// <param name="className">CSS class name</param>
        /// <returns>Reference to current instance</returns>
        public HTMLWriter AppendSub(string content, string className = "")
        {
            return AppendTagStart("sub", className).AppendText(content).AppendTagEnd();
        }
        /// <summary>
        /// Appends an html 'sup' tag with content and CSS class name
        /// </summary>
        /// <param name="content">Text content</param>
        /// <param name="className">CSS class name</param>
        /// <returns>Reference to current instance</returns>
        public HTMLWriter AppendSup(string content, string className = "")
        {
            return AppendTagStart("sup", className).AppendText(content).AppendTagEnd();
        }
        /// <summary>
        /// Appends an html 'pre' tag with content and CSS class name
        /// </summary>
        /// <param name="content">Text content</param>
        /// <param name="className">CSS class name</param>
        /// <returns>Reference to current instance</returns>
        public HTMLWriter AppendPre(string content, string className = "")
        {
            return AppendTagStart("pre", className).AppendText(content).AppendTagEnd();
        }
        /// <summary>
        /// Appends an html 'q' tag with content
        /// </summary>
        /// <param name="content">Text content</param>
        /// <returns>Reference to current instance</returns>
        public HTMLWriter AppendQuote(string content)
        {
            return AppendTagStart("q").AppendText(content).AppendTagEnd();
        }
        /// <summary>
        /// Appends an html 'i' tag with content
        /// </summary>
        /// <param name="content">Text content</param>
        /// <returns>Reference to current instance</returns>
        public HTMLWriter AppendItalicText(string content)
        {
            return AppendTagStart("i").AppendText(content).AppendTagEnd();
        }
        /// <summary>
        /// Appends an html 'b' tag with content
        /// </summary>
        /// <param name="content">Text content</param>
        /// <returns>Reference to current instance</returns>
        public HTMLWriter AppendBoldText(string content)
        {
            return AppendTagStart("b").AppendText(content).AppendTagEnd();
        }
        /// <summary>
        /// Appends an html 'sup' tag with content and CSS class name
        /// </summary>
        /// <param name="content">Text content</param>
        /// <param name="className">CSS class name</param>
        /// <returns>Reference to current instance</returns>
        public HTMLWriter AppendCode(string content, string className = "")
        {
            return AppendTagStart("code", className).AppendText(content).AppendTagEnd();
        }
        /// <summary>
        /// Appends an html 'acronym' tag with content and CSS class name
        /// </summary>
        /// <param name="content">Text content</param>
        /// <param name="className">CSS class name</param>
        /// <returns>Reference to current instance</returns>
        public HTMLWriter AppendAcronym(string content, string className = "")
        {
            return AppendTagStart("acronym", className).AppendText(content).AppendTagEnd();
        }
        /// <summary>
        /// Appends an html 'abbr' tag with content and CSS class name
        /// </summary>
        /// <param name="content">Text content</param>
        /// <param name="className">CSS class name</param>
        /// <returns>Reference to current instance</returns>
        public HTMLWriter AppendAbbreviation(string content, string className = "")
        {
            return AppendTagStart("abbr", className).AppendText(content).AppendTagEnd();
        }
        /// <summary>
        /// Appends an html 'address' tag with content and CSS class name
        /// </summary>
        /// <param name="content">Text content</param>
        /// <param name="className">CSS class name</param>
        /// <returns>Reference to current instance</returns>
        public HTMLWriter AppendAddress(string content, string className = "")
        {
            return AppendTagStart("address", className).AppendText(content).AppendTagEnd();
        }
        /// <summary>
        /// Appends an html 'address' start tag with CSS class name
        /// </summary>
        /// <param name="className">CSS class name</param>
        /// <returns>Reference to current instance</returns>
        public HTMLWriter AppendAddressStart(string className = "")
        {
            return AppendTagStart("address", className);
        }
        /// <summary>
        /// Appends an html 'nobr' tag with content and CSS class name
        /// </summary>
        /// <param name="content">Text content</param>
        /// <param name="className">CSS class name</param>
        /// <returns>Reference to current instance</returns>
        public HTMLWriter AppendNOBR(string content, string className = "")
        {
            return AppendTagStart("nobr", className).AppendText(content).AppendTagEnd();
        }
        /// <summary>
        /// Appends an html 'nobr' start tag with CSS class name
        /// </summary>
        /// <param name="className">CSS class name</param>
        /// <returns>Reference to current instance</returns>
        public HTMLWriter AppendNOBRStart(string className = "")
        {
            return AppendTagStart("nobr", className);
        }
        #endregion

        #region Div
        /// <summary>
        /// Appends an html 'div' start tag with CSS class name
        /// </summary>
        /// <param name="className">CSS class name</param>
        /// <returns>Reference to current instance</returns>
        public HTMLWriter AppendDivStart(string className = "")
        {
            return AppendTagStart("div", className);
        }
        #endregion

        #region Span
        /// <summary>
        /// Appends an html 'span' start tag with CSS class name
        /// </summary>
        /// <param name="className">CSS class name</param>
        /// <returns>Reference to current instance</returns>
        public HTMLWriter AppendSpanStart(string className = "")
        {
            return AppendTagStart("span", className);
        }
        /// <summary>
        /// Appends an html 'span' start tag with CSS class name and custom attributes
        /// </summary>
        /// <param name="className">CSS class name</param>
        /// <param name="customAttributes">Array of custom attributes</param>
        /// <returns>Reference to current instance</returns>
        public HTMLWriter AppendSpanStart(string className, params CustomAttribute[] customAttributes)
        {
            return AppendTagStart("span", className, customAttributes);
        }
        #endregion

        #region Horisontal line
        /// <summary>
        /// Appends an html 'hr' tag
        /// </summary>
        /// <returns>Reference to current instance</returns>
        public HTMLWriter AppendHorisontalLine()
        {
            return AppendNewLineWithIndentation()
                .AppendText("<hr />")
                .AppendNewLineWithIndentation();
        }
        #endregion

        #region Line break
        /// <summary>
        /// Appends an html 'br' tag
        /// </summary>
        /// <returns>Reference to current instance</returns>
        public HTMLWriter AppendLineBreak()
        {
            return AppendText("<br />");
        }
        #endregion

        #region Headings
        /// <summary>
        /// Appends an html heading (e.g. 'h1') tag with content and CSS class name
        /// </summary>
        /// <param name="level">Heading level</param>
        /// <param name="content">Text content</param>
        /// <param name="className">CSS class name</param>
        /// <returns>Reference to current instance</returns>
        public HTMLWriter AppendHeading(int level, string content, string className = "")
        {
            return AppendTagStart("h" + level.ToString(), className).AppendText(content).AppendTagEnd();
        }
        #endregion

        #region Table
        /// <summary>
        /// Appends an html 'table' start tag with CSS class name and custom attributes
        /// </summary>
        /// <param name="className">CSS class name</param>
        /// <param name="customAttributes">Custom html attributes</param>
        /// <returns>Reference to current instance</returns>
        public HTMLWriter AppendTableStart(string className = "", params CustomAttribute[] customAttributes)
        {
            return AppendTagStart("table", className, customAttributes);
        }
        #region Table header
        /// <summary>
        /// Appends an html 'thead' start tag with CSS class name and custom attributes
        /// </summary>
        /// <param name="className">CSS class name</param>
        /// <param name="customAttributes">Custom html attributes</param>
        /// <returns>Reference to current instance</returns>
        public HTMLWriter AppendTableHeadStart(string className = "", params CustomAttribute[] customAttributes)
        {
            return AppendTagStart("thead", className, customAttributes);
        }
        #endregion
        #region Table footer
        /// <summary>
        /// Appends an html 'tfoot' start tag with CSS class name and custom attributes
        /// </summary>
        /// <param name="className">CSS class name</param>
        /// <param name="customAttributes">Custom html attributes</param>
        /// <returns>Reference to current instance</returns>
        public HTMLWriter AppendTableFootStart(string className = "", params CustomAttribute[] customAttributes)
        {
            return AppendTagStart("tfoot", className, customAttributes);
        }
        #endregion
        #region Table body
        /// <summary>
        /// Appends an html 'tbody' start tag with CSS class name and custom attributes
        /// </summary>
        /// <param name="className">CSS class name</param>
        /// <param name="customAttributes">Custom html attributes</param>
        /// <returns>Reference to current instance</returns>
        public HTMLWriter AppendTableBodyStart(string className = "", params CustomAttribute[] customAttributes)
        {
            return AppendTagStart("tbody", className, customAttributes);
        }
        #endregion
        #region Table row
        /// <summary>
        /// Appends an html 'tr' start tag with CSS class name
        /// </summary>
        /// <param name="className">CSS class name</param>
        /// <returns>Reference to current instance</returns>
        public HTMLWriter AppendTableRowStart(string className = "")
        {
            //AppendNewLineWithIndentation();
            return AppendTagStart("tr", className);//.AutoIndentationInc();
        }
        #endregion
        #region  Table cell header
        /// <summary>
        /// Appends an html 'th' start tag with CSS class name and custom attributes
        /// </summary>
        /// <param name="className">CSS class name</param>
        /// <param name="customAttributes">Custom html attributes</param>
        /// <returns>Reference to current instance</returns>
        public HTMLWriter AppendTableHeaderCellStart(string className = "", params CustomAttribute[] customAttributes)
        {
            //AppendNewLineWithIndentation();
            return AppendTagStart("th", className, customAttributes);
        }
        /// <summary>
        /// Appends an html 'th' tag with content with CSS class name and custom attributes
        /// </summary>
        /// <param name="content">Text content</param>
        /// <param name="className">CSS class name</param>
        /// <param name="customAttributes">Custom html attributes</param>
        /// <returns>Reference to current instance</returns>
        public HTMLWriter AppendTableHeaderCell(string content, string className = "", params CustomAttribute[] customAttributes)
        {
            return AppendTagStart("th", className, customAttributes)
                .AppendText(content)
                .AppendTagEnd();
        }
        #endregion
        #region Table cell
        /// <summary>
        /// Appends an html 'td' start tag with CSS class name and custom attributes
        /// </summary>
        /// <param name="className">CSS class name</param>
        /// <param name="customAttributes">Custom html attributes</param>
        /// <returns>Reference to current instance</returns>
        public HTMLWriter AppendTableCellStart(string className = "", params CustomAttribute[] customAttributes)
        {
            return AppendTagStart("td", className, customAttributes);
        }
        /// <summary>
        /// Appends an html 'td' tag with content with CSS class name and custom attributes
        /// </summary>
        /// <param name="content">Text content</param>
        /// <param name="className">CSS class name</param>
        /// <param name="customAttributes">Custom html attributes</param>
        /// <returns>Reference to current instance</returns>
        public HTMLWriter AppendTableCell(string content, string className = "", params CustomAttribute[] customAttributes)
        {
            return AppendTagStart("td", className, customAttributes)
                .AppendText(content)
                .AppendTagEnd();
        }
        #endregion
        #endregion

        #region Lists
        /// <summary>
        /// Appends an html 'ul' start tag with CSS class name
        /// </summary>
        /// <param name="className">CSS class name</param>
        /// <returns>Reference to current instance</returns>
        public HTMLWriter AppendUnOrderedListStart(string className = "")
        {
            return AppendTagStart("ul", className);
        }
        /// <summary>
        /// Appends an html 'ol' start tag with CSS class name
        /// </summary>
        /// <param name="className">CSS class name</param>
        /// <returns>Reference to current instance</returns>
        public HTMLWriter AppendOrderedListStart(string className = "")
        {
            return AppendTagStart("ol", className);
        }
        /// <summary>
        /// Appends an html 'li' tag with content and CSS class name
        /// </summary>
        /// <param name="content">Text content</param>
        /// <param name="className">CSS class name</param>
        /// <returns>Reference to current instance</returns>
        public HTMLWriter AppendListItem(string content, string className = "")
        {
            return AppendTagStart("li", className).AppendText(content).AppendTagEnd();
        }
        /// <summary>
        /// Appends an html 'dl' start tag with CSS class name
        /// </summary>
        /// <param name="className">CSS class name</param>
        /// <returns>Reference to current instance</returns>
        public HTMLWriter AppendDefinitionListStart(string className = "")
        {
            return AppendTagStart("dl", className);
        }
        /// <summary>
        /// Appends an html 'dt' tag with content and CSS class name
        /// </summary>
        /// <param name="content">Text content</param>
        /// <param name="className">CSS class name</param>
        /// <returns>Reference to current instance</returns>
        public HTMLWriter AppendDefinitionTerm(string content, string className = "")
        {
            return AppendTagStart("dt", className).AppendText(content).AppendTagEnd();
        }
        /// <summary>
        /// Appends an html 'dt' tag with content and CSS class name
        /// </summary>
        /// <param name="content">Text content</param>
        /// <param name="className">CSS class name</param>
        /// <returns>Reference to current instance</returns>
        public HTMLWriter AppendDefinitionDescription(string content, string className = "")
        {
            return AppendTagStart("dd", className).AppendText(content).AppendTagEnd();
        }
        #endregion

        #region blockquote
        /// <summary>
        /// Appends an html 'span' start tag
        /// </summary>
        /// <returns>Reference to current instance</returns>
        public HTMLWriter AppendBlockquoteStart(string className = "")
        {
            return AppendTagStart("blockquote", className);
        }
        #endregion

        #region Anchor
        /// <summary>
        /// Appends an html 'a' start tag with url, CSS class name, url title and target
        /// </summary>
        /// <param name="url">Target url</param>
        /// <param name="className">CSS class name</param>
        /// <param name="title">Title of link</param>
        /// <param name="target">html frame or window target name</param>
        /// <returns>Reference to current instance</returns>
        public HTMLWriter AppendAnchorStart(string url, string className = "", string title = "", string target = "")
        {
            return AppendTagStart("a", className,
                new CustomAttribute("href", url),
                new CustomAttribute("title", title),
                new CustomAttribute("target", target));
        }
        /// <summary>
        /// Appends an html 'a' tag with url, content and CSS class name
        /// </summary>
        /// <param name="url">Target url</param>
        /// <param name="content">Text content</param>
        /// <param name="className">CSS class name</param>
        /// <returns>Reference to current instance</returns>
        public HTMLWriter AppendAnchor(string url, string content, string className = "")
        {
            return AppendTagStart("a", className, new CustomAttribute("href", url))
                .AppendText(content)
                .AppendTagEnd();
        }
        #endregion

        #region Image
        /// <summary>
        /// Appends an html 'img' tag with image source url, title and custom attributes
        /// </summary>
        /// <param name="source">Image source url</param>
        /// <param name="title">Image title</param>
        /// <param name="customAttributes">Custom html attributes</param>
        /// <returns>Reference to current instance</returns>
        public HTMLWriter AppendImage(string source, string title = "", params CustomAttribute[] customAttributes)
        {
            List<CustomAttribute> ca = new List<CustomAttribute>();
            ca.AddRange(customAttributes);
            ca.AddRange(new CustomAttribute[] 
                { 
                    new CustomAttribute("src", source),
                    new CustomAttribute("alt", title) 
                });
            return AppendTagStart("img", ca.ToArray())
                .AppendTagEnd();
        }
        #endregion

        #region Iframe
        /// <summary>
        /// Appends an html 'iframe' tag with some attributes
        /// </summary>
        /// <param name="source">Frame source url</param>
        /// <param name="name">Frame name</param>
        /// <param name="width">Frame width</param>
        /// <param name="height">Frame height</param>
        /// <param name="frameborder">Frame border visible?</param>
        /// <param name="customAttributes">Custom html attributes</param>
        /// <returns>Reference to current instance</returns>
        public HTMLWriter AppendIframe(string source, string name,
            string width, string height, bool frameborder = true,
            params CustomAttribute[] customAttributes)
        {
            List<CustomAttribute> ca = new List<CustomAttribute>();
            ca.AddRange(customAttributes);
            ca.AddRange(new CustomAttribute[] 
                { 
                    new CustomAttribute("src", source),
                    new CustomAttribute("name", name),
                    new CustomAttribute("width", width),
                    new CustomAttribute("height", height),
                    new CustomAttribute("frameborder", frameborder ? "1" : "0")
                });
            return AppendTagStart("iframe", ca.ToArray()).AppendTagEnd();
        }
        #endregion 
        #endregion

        #region Stylesheets
        #region Internal Stylesheet
        /// <summary>
        /// Appends stylesheet information to internal stylesheet stream
        /// </summary>
        /// <param name="styleName">Name of tag and/or class and/or id for CSS</param>
        /// <param name="styleContent">Style description</param>
        /// <returns>Reference to current instance</returns>
        public HTMLWriter AppendStyle(string styleName, string styleContent)
        {
            AppendStyleInternal(styleName, styleContent);
            return this;
        }
        #endregion

        #region External stylesheets
        /// <summary>
        /// Appends external stylesheet to list of external stylesheets
        /// </summary>
        /// <param name="url">URL of CSS file</param>
        /// <returns>Reference to current instance</returns>
        public HTMLWriter AppendExternalStylesheet(string url)
        {
            AppendExternalStylesheetInternal(url);
            return this;
        }
        #endregion
        #endregion
    }
}
